package com.dxtx.widget.tagcloud;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;

import com.dxtx.common.R;


/**
 * Copyright © 2016 moxun
 * <p/>
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the “Software”),
 * to deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * <p/>
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * <p/>
 * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
 * OR OTHER DEALINGS IN THE SOFTWARE.
 */


public class TagCloudView extends FrameLayout implements Runnable, TagsAdapter.OnDataSetChangeListener {
    //    private final float TOUCH_SCALE_FACTOR = .8f;
    private final float TOUCH_SCALE_FACTOR = 12f;
    private final float TRACKBALL_SCALE_FACTOR = 10;
    private float tspeed = 2f;
    private TagCloud mTagCloud;
    private float mAngleX = 0.5f;
    private float mAngleY = 0.5f;
    private float centerX, centerY;
    private float radius;
    private float radiusPercent = 0.9f;

    private float[] darkColor = new float[]{1f, 0f, 0f, 1};
    private float[] lightColor = new float[]{0.9412f, 0.7686f, 0.2f, 1};
    private int sphereColor = Color.WHITE;

    private State touch_state = State.Scroll;
    private Tag selectTag;
    private int selectIndex;

    private float minLockError;

    private enum State {
        Touch, Calm, AutoLock, Scroll
    }

    private boolean autoSelect = false;

    private Handler handler = new Handler(Looper.getMainLooper());

    private TagsAdapter tagsAdapter;

    Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    private Paint glancePaint;
    private RectF glanceRect = new RectF();
    private RectF shadowRect = new RectF();
    private Bitmap glanceBitmap, shadowBitmap;

    public TagCloudView(Context context) {
        super(context);
        setFocusableInTouchMode(true);
        init(context, null);
    }

    public TagCloudView(Context context, AttributeSet attrs) {
        super(context, attrs);
        setFocusableInTouchMode(true);
        init(context, attrs);
    }

    public TagCloudView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setFocusableInTouchMode(true);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        mTagCloud = new TagCloud();
        if (attrs != null) {
            TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.TagCloudView);

            int light = typedArray.getColor(R.styleable.TagCloudView_lightColor, Color.GREEN);
            setLightColor(light);

            int dark = typedArray.getColor(R.styleable.TagCloudView_darkColor, Color.GRAY);
            setDarkColor(dark);

            sphereColor = typedArray.getColor(R.styleable.TagCloudView_sphereColor, Color.WHITE);

            autoSelect = typedArray.getBoolean(R.styleable.TagCloudView_autoLockSelect, autoSelect);

            float p = typedArray.getFloat(R.styleable.TagCloudView_radiusPercent, radiusPercent);
            setRadiusPercent(p);

            float s = typedArray.getFloat(R.styleable.TagCloudView_scrollSpeed, 2f);
            setScrollSpeed(s);
        }
        paint.setColor(sphereColor);
        glancePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        glancePaint.setDither(true);
        glancePaint.setFilterBitmap(true);
        glancePaint.setAlpha(200);
        glanceBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ball_light);
        shadowBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.shadow);
        minLockError = getResources().getDimension(R.dimen._1dp);
    }


    public final void setAdapter(TagsAdapter adapter) {
        tagsAdapter = adapter;
        tagsAdapter.setOnDataSetChangeListener(this);
        onChange();
        getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                getViewTreeObserver().removeGlobalOnLayoutListener(this);
                //设置每个item的旋转中心
                for (int i = 0; i < getChildCount(); i++) {
                    View child = getChildAt(i);
                    child.setPivotX(child.getMeasuredWidth() / 2);
                    child.setPivotY(child.getMeasuredHeight() / 2);
                }
                updateChild();
            }
        });
    }

    public void setLightColor(int color) {
        float[] argb = new float[4];
        argb[0] = Color.alpha(color) / 1.0f / 255;
        argb[1] = Color.red(color) / 1.0f / 255;
        argb[2] = Color.green(color) / 1.0f / 255;
        argb[3] = Color.blue(color) / 1.0f / 255;

        lightColor = argb.clone();
        onChange();
    }

    public void setDarkColor(int color) {
        float[] argb = new float[4];
        argb[0] = Color.alpha(color) / 1.0f / 255;
        argb[1] = Color.red(color) / 1.0f / 255;
        argb[2] = Color.green(color) / 1.0f / 255;
        argb[3] = Color.blue(color) / 1.0f / 255;

        darkColor = argb.clone();
        onChange();
    }

    public void setRadiusPercent(float percent) {
        if (percent > 1f || percent < 0f) {
            throw new IllegalArgumentException("percent value not in range 0 to 1");
        } else {
            radiusPercent = percent;
            onChange();
        }
    }


    private void initFromAdapter() {
        this.postDelayed(new Runnable() {
            @Override
            public void run() {

                mTagCloud.setRadius((int) radius);

                mTagCloud.setTagColorLight(lightColor);//higher color
                mTagCloud.setTagColorDark(darkColor);//lower color

                mTagCloud.clear();
                removeAllViews();
                for (int i = 0; i < tagsAdapter.getCount(); i++) {
                    TagCloudView.this.mTagCloud.add(new Tag(tagsAdapter.getPopularity(i)));
                    addView(tagsAdapter.getView(getContext(), i, TagCloudView.this));
                }

                mTagCloud.create(true);

                mTagCloud.setAngleX(mAngleX);
                mTagCloud.setAngleY(mAngleY);
                mTagCloud.update();
            }
        }, 0);
    }

    public void setScrollSpeed(float scrollSpeed) {
        tspeed = scrollSpeed;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int modeW = MeasureSpec.getMode(widthMeasureSpec);
        int modeH = MeasureSpec.getMode(heightMeasureSpec);
        if (modeW == MeasureSpec.EXACTLY) {
            heightMeasureSpec = widthMeasureSpec;
        } else if (modeH == MeasureSpec.EXACTLY) {
            widthMeasureSpec = heightMeasureSpec;
        }
        //宽高都wrap_content,指定最大值,则用最大值
        else if (modeW == MeasureSpec.AT_MOST && modeH == MeasureSpec.AT_MOST) {
            int size = Math.min(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(widthMeasureSpec));
            widthMeasureSpec = heightMeasureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
        } else if (modeW == MeasureSpec.AT_MOST) {
            int size = MeasureSpec.getSize(widthMeasureSpec);
            widthMeasureSpec = heightMeasureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
        } else if (modeH == MeasureSpec.AT_MOST) {
            int size = MeasureSpec.getSize(heightMeasureSpec);
            widthMeasureSpec = heightMeasureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
        }
        //如果父控件让我宽高都随意,则我也随意
        else {
            widthMeasureSpec = heightMeasureSpec = MeasureSpec.makeMeasureSpec(600, MeasureSpec.EXACTLY);
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        handler.post(this);
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        handler.removeCallbacksAndMessages(null);
    }

    private void updateChild() {
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                Tag tag = mTagCloud.get(i);
                float scale = tag.getScale();
                child.setScaleX(scale);
                child.setScaleY(scale);
                if (scale < 1 && scale > 0.9f) {//[0.9,1]短暂区间的强制转为区间[0.1,1]
                    scale = 9 * scale - 8;
                } else if (scale <= 0.9f) {
                    scale = 0.1f;//让转到背面的看起来模糊,
                }
                child.setAlpha(scale);

                int left, top;
                left = (int) (centerX + tag.x - child.getMeasuredWidth() / 2f);
                top = (int) (centerY + tag.y - child.getMeasuredHeight() / 2f);

                child.setX(left);
                child.setY(top);
                double rx = Math.sqrt(radius * radius - tag.x * tag.x);
                float rotationX = -(float) (Math.asin(tag.y / rx) * 180 / Math.PI);
                float rotationY = -(float) (Math.asin(-tag.x / radius) * 180 / Math.PI);
                child.setRotationX(rotationX);
                child.setRotationY(rotationY);
            }
        }
        invalidate();
    }

    public void reset() {
        mTagCloud.reset();
        updateChild();
    }

    @Override
    public boolean onTrackballEvent(MotionEvent e) {
        float x = e.getX();
        float y = e.getY();

        mAngleX = (y) * tspeed * TRACKBALL_SCALE_FACTOR;
        mAngleY = (-x) * tspeed * TRACKBALL_SCALE_FACTOR;

        mTagCloud.setAngleX(mAngleX);
        mTagCloud.setAngleY(mAngleY);
        mTagCloud.update();

        updateChild();
        return true;
    }

    float x0, y0, downX, downY;

    @Override
    public boolean dispatchTouchEvent(MotionEvent e) {
        super.dispatchTouchEvent(e);
        float x = e.getX();
        float y = e.getY();

        switch (e.getAction()) {
            case MotionEvent.ACTION_DOWN:
                touch_state = State.Touch;
                x0 = x;
                y0 = y;
                break;
            case MotionEvent.ACTION_MOVE:
                //rotate elements depending on how far the selection point is from center of cloud
//                float dx = x - centerX;
//                float dy = y - centerY;
                if (selectTag != null) {
                    selectTag = null;
                    getChildAt(selectIndex).setSelected(false);
                }
                float dx = x - x0;
                float dy = y - y0;
                x0 = x;
                y0 = y;
                mAngleX = (dy / radius) * tspeed * TOUCH_SCALE_FACTOR;
                mAngleY = (-dx / radius) * tspeed * TOUCH_SCALE_FACTOR;
                processTouch();

                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                //开始惯性滚动
                touch_state = State.Scroll;
                break;
        }

        return true;
    }

    private boolean intercept = false;

    @Override
    public boolean onInterceptTouchEvent(MotionEvent e) {
        float x = e.getX();
        float y = e.getY();
        switch (e.getAction()) {
            case MotionEvent.ACTION_DOWN:
//                touch_state = State.Touch;
                downX = x;
                downY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                if (intercept) {
                    return true;
                }
                float dx = x - downX;
                float dy = y - downY;
                if (Math.sqrt(dx * dx + dy * dy) > 100) {//滑动超过一定距离,拦截事件
                    return intercept = true;
                }
                break;
            case MotionEvent.ACTION_UP:
                boolean temp = intercept;
                intercept = false;
                return temp;
        }
        return false;
    }

    private void processTouch() {
        if (mTagCloud != null) {
            mTagCloud.setAngleX(mAngleX);
            mTagCloud.setAngleY(mAngleY);
            mTagCloud.update();
        }
        updateChild();
    }

    @Override
    public void onChange() {
        if (!isInEditMode())
            initFromAdapter();
    }

    @Override
    public void run() {
        if (touch_state == State.Scroll) {
            //是否已经停止滚动
            boolean stop = mAngleX == 0 && mAngleY == 0;
            if (mAngleX > 0.4f) {
                mAngleX -= 0.1f;
            } else if (mAngleX < -0.4f) {
                mAngleX += 0.1f;
            } else {
                mAngleX = 0;
            }
            if (mAngleY > 0.4f) {
                mAngleY -= 0.1f;
            } else if (mAngleY < -0.4f) {
                mAngleY += 0.1f;
            } else {
                mAngleY = 0;
            }
            if (autoSelect && !stop && mAngleX == 0 && mAngleY == 0) {
                startAutoLockItem();
            }
            processTouch();

        } else if (touch_state == State.AutoLock) {

            float absX = Math.abs(selectTag.x);
            float absY = Math.abs(selectTag.y);
            //是否在误差范围内
            boolean locked = selectTag == null || (absX < 2 && absY < 2);
            if (locked) {
                //锁定完成,恢复平静---
                mAngleX = mAngleY = 0;
                touch_state = State.Calm;
                handler.postDelayed(this, 16);
                View child = getChildAt(selectIndex);
                child.setSelected(true);
                if (onItemSelectListener != null) {
                    onItemSelectListener.onSelectLockedComplete(child, selectIndex, selectTag);
                }
                return;
            }
            if (absX > absY) {
                if (absX < minLockError) {
                    mAngleX = mAngleY = 0;
                } else {
                    mAngleY = Math.max(absX / radius * 3.5f, .5f);
                    mAngleX = absY / absX * mAngleY;
                    mAngleY *= selectTag.x / absX;
                    mAngleX *= -selectTag.y / absY;
                }
            } else {
                if (absY < minLockError) {
                    mAngleY = mAngleX = 0;
                } else {
                    mAngleX = Math.max(absY / radius * 3.5f, .5f);
                    mAngleY = absX / absY * mAngleX;
                    mAngleX *= -selectTag.y / absY;
                    mAngleY *= selectTag.x / absX;
                }
            }
            processTouch();
        }
        handler.postDelayed(this, 16);
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        //绘制影子
        canvas.drawBitmap(shadowBitmap, null, shadowRect, glancePaint);
        //绘制地球仪
        canvas.drawCircle(centerX, centerY, radius, paint);

        //绘制光照
        canvas.drawBitmap(glanceBitmap, null, glanceRect, glancePaint);
        super.dispatchDraw(canvas);

        //画线
       /* List<Tag> list = mTagCloud.getTagList();
        if (list != null && !list.isEmpty()) {
            Path path = new Path();
            Tag lastTag = mTagCloud.get(0);
            path.moveTo(centerX + lastTag.getX(), centerY + lastTag.getY());
            for (int i = 1; i < getChildCount(); i++) {
                Tag tag = mTagCloud.get(i);
                path.lineTo(centerX + tag.x, centerY + tag.y);
            }
            paint.setStyle(Paint.Style.STROKE);
            paint.setStrokeWidth(4);
            canvas.drawPath(path, paint);
        }*/


    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        centerX = w / 2;
        centerY = h / 2;
        radius = radiusPercent * Math.min(centerX, centerY);
        glanceRect.set(centerX - radius, centerY - radius, centerX + radius, centerY + radius);
        shadowRect.set(centerX, centerY + radius * 0.8f, w, centerY + radius * 1.05f);
    }

    //开始自动锁定item
    private void startAutoLockItem() {

        if (selectTag != null) {
            getChildAt(selectIndex).setSelected(false);
            selectTag = null;
        }
        //选出距离最近的item
        float max = Integer.MAX_VALUE;
        for (int i = 0; i < getChildCount(); i++) {
            Tag tag = mTagCloud.get(i);
            if (tag.getScale() > 0.9) {
                double distance = tag.distance();
                if (distance < max) {
                    max = (float) distance;
                    selectTag = tag;
                    selectIndex = i;
                }
            }
        }
        if (selectTag != null) {
            touch_state = State.AutoLock;
            if (onItemSelectListener != null) {
                onItemSelectListener.onSelect(getChildAt(selectIndex), selectIndex, selectTag);
            }
        }
    }

    public void setOnItemSelectListener(OnItemSelectListener onItemSelectListener) {
        this.onItemSelectListener = onItemSelectListener;
    }

    private OnItemSelectListener onItemSelectListener;

    public interface OnItemSelectListener {
        void onSelect(View child, int position, Tag tag);

        void onSelectLockedComplete(View child, int position, Tag tag);
    }
}
